package com.skyline.energy.utils;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import com.skyline.common.reflection.GenericTypeUtils;
import com.skyline.common.reflection.TypeUtils;
import com.skyline.energy.exception.DaoGenerateException;

/**
 * 用于将Annotation解析成Definition时使用的工具类
 * 
 * @author wuqh
 * 
 */
public class DefinitionParseUtils {

	/**
	 * 解析出bean的getter方法,和parameterNames中个参数对应的args中的索引值
	 * 由于parameterNames包含"."，所以需要indexes记录"."之前的@Param对应的位置
	 * 
	 * <pre>
	 * 例如：
	 * <code>@CacheDelete(key="album-d-:albumId-:ownerId")</code>
	 * <code>@VerUpdate(vkey="ALBUM-:owner.id-:album.id-v")</code>
	 * public void updatePhoto(@Param("owner") User owner, @Param("album") Album album);</code>
	 * 将传入：parameterNames=["album.id","owner.id"]，paramIndexes={"album"=1,"owner"=0},paramTypes=[User.class,Album.class]
	 * 生成：[[User.getId,Album.getId],[1,0]]
	 * </pre>
	 * 
	 * 
	 * @param parameterNames
	 * @param paramIndexes
	 * @param paramTypes
	 * @return
	 * @throws DaoGenerateException
	 */
	public static Object[] getGettersAndIndexes(List<String> parameterNames, Map<String, Integer> paramIndexes,
			Class<?>[] paramTypes) throws DaoGenerateException {
		return getGettersAndIndexes(null, parameterNames, paramIndexes, null, paramTypes);
	}

	/**
	 * 查找Bean中指定属性的getter方法。如果找不到将抛出DaoGenerateException
	 * 
	 * @param clazz
	 *            Bean的类型
	 * @param prop
	 *            属性名称
	 * @return
	 * @throws DaoGenerateException
	 */
	private static Method findGetterByPropertyName(Class<?> clazz, String prop) throws DaoGenerateException {
		if (TypeUtils.isTypeMap(clazz)) {
			return null;
		}
		String name = Character.toUpperCase(prop.charAt(0)) + prop.substring(1);
		String getter = "get" + name;
		String is = "is" + name;
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			Class<?> returnType = method.getReturnType();
			if (method.getParameterTypes().length == 0 && !returnType.equals(void.class)) {
				if (getter.equals(method.getName())) {
					return method;
				}
				if (is.equals(method.getName())
						&& (returnType.equals(boolean.class) || returnType.equals(Boolean.class))) {
					return method;
				}
			}
		}
		throw new DaoGenerateException("无法获取[" + clazz.getName() + "]中[" + prop + "]属性的getter方法");
	}

	/**
	 * 解析出bean的getter方法,和parameterNames中个参数对应的args中的索引值
	 * 由于parameterNames包含"."，所以需要indexes记录"."之前的@Param，@BatchParam对应的位置
	 * 
	 * <pre>
	 * 例如：
	 * <code>@BatchUpdate("insert into photo(ownerId, albumId, file) values (:user.id, :album.id, :photo.file)")</code>
	 * <code>@ReturnId
	 * public List<Long> insertPhotosReturnIds(@Param("user") User user, @Param("album") Album album, @BatchParam("photo") Photo[] photo);</code>
	 * 将传入：parameterNames=["owner.id","album.id","photo.file"]，paramIndexes={""owner"=0,album"=1},batchParamIndexes={"photo"=2},paramTypes=[User.class,Album.class,Photo.class]
	 * 生成：[[User.getId,Album.getId,Photo.getFile],[0,1,2]]
	 * </pre>
	 * 
	 * 
	 * @param parameterNames
	 * @param paramIndexes
	 * @param paramTypes
	 * @return
	 * @throws DaoGenerateException
	 */
	public static Object[] getGettersAndIndexes(Method method, List<String> parameterNames,
			Map<String, Integer> paramIndexes, Map<String, Integer> batchParamIndexes, Class<?>[] paramTypes)
			throws DaoGenerateException {
		int length = parameterNames.size();
		
		Method[] getters = new Method[length];
		Integer[] parameterIndexes = new Integer[length];
		
		for (int i = 0; i < length; i++) {
			String paramName = parameterNames.get(i);
			
			if (isNeedFindGetter(paramName)) {
				String actualName = getParamName(paramName);

				checkParamName(paramIndexes, batchParamIndexes, actualName);

				Class<?> componentType;
				Integer index;
				if (paramIndexes.containsKey(actualName)) {
					index = paramIndexes.get(actualName);
					componentType = paramTypes[index];
				} {
					index = batchParamIndexes.get(actualName);

					checkBatchParamType(paramName, paramTypes, index);

					componentType = getBatchParamComponentType(method, paramTypes, index);

					checkBatchParamComponentType(paramName, componentType);

				}

				parameterIndexes[i] = index;
				
				String prop = getPropName(paramName);
				Method getter = findGetterByPropertyName(componentType, prop);
				getters[i] = getter;

			} else { // don't need getters
				getters[i] = null;

				checkParamName(paramIndexes, batchParamIndexes, paramName);

				if (paramIndexes.containsKey(paramName)) {
					setParameterIndex(paramIndexes, parameterIndexes, i, paramName);
				} else {
					setParameterIndex(batchParamIndexes, parameterIndexes, i, paramName);
				}
			}
		}
		return new Object[] { getters, parameterIndexes };
	}

	private static String getPropName(String paramName) {
		int pos = paramName.indexOf('.');
		String prop = paramName.substring(pos + 1);
		return prop;
	}

	private static String getParamName(String paramName) {
		int pos = paramName.indexOf('.');
		String actualName = paramName.substring(0, pos);
		return actualName;
	}

	private static boolean isNeedFindGetter(String paramName) {
		int pos = paramName.indexOf('.');
		return pos != -1;
	}

	private static void setParameterIndex(Map<String, Integer> paramIndexes, Integer[] parameterIndexes, int i,
			String paramName) {
		Integer index = paramIndexes.get(paramName);
		parameterIndexes[i] = index;
	}

	private static Class<?> getBatchParamComponentType(Method method, Class<?>[] paramTypes, Integer index) {
		Class<?> paramType = paramTypes[index];
		Class<?> componentType;
		if (paramType.isArray()) {
			componentType = paramTypes[index].getComponentType();
		} else {
			componentType = GenericTypeUtils.getParameterGenericType(method, index);
		}
		return componentType;
	}

	private static void checkBatchParamComponentType(String paramName, Class<?> componentType)
			throws DaoGenerateException {
		if (componentType == null) {
			throw new DaoGenerateException("@BatchParam(\"" + paramName + "\")只能用于泛型类型清晰的Collection实现类上");
		}
	}

	private static void checkBatchParamType(String paramName, Class<?>[] paramTypes, Integer index)
			throws DaoGenerateException {
		Class<?> paramType = paramTypes[index];
		if (!paramType.isArray() && !TypeUtils.isTypeList(paramType)) {
			throw new DaoGenerateException("@BatchParam(\"" + paramName + "\")只能用于数组类型或者List实现类的参数上");
		}
	}

	private static void checkParamName(Map<String, Integer> paramIndexes, Map<String, Integer> batchParamIndexes,
			String paramlName) throws DaoGenerateException {
		
		boolean isInParam = paramIndexes.containsKey(paramlName);
		boolean hasBatchParam = batchParamIndexes != null;
		boolean isInBatchParam = (hasBatchParam && batchParamIndexes.containsKey(paramlName));
		
		if (!isInParam && !isInBatchParam) {
			String errMsg = "方法参数中必须包含@Param(\"" + paramlName + "\")注解";
			if(hasBatchParam) {
				errMsg = errMsg + "或者@BatchParam(\"" + paramlName + "\")注解";
			}
			throw new DaoGenerateException(errMsg);
		}
	}

}
